package com.kgcx.schedule.service;

import com.kgcx.common.client.organization.DriverClient;
import com.kgcx.common.client.organization.pojo.Driver;
import com.kgcx.common.client.price.MileageLocationClient;
import com.kgcx.common.constant.Cons;
import com.kgcx.common.constant.ErrorCode;
import com.kgcx.common.utils.GaodeApi;
import com.kgcx.common.utils.SmsUtils;
import com.kgcx.core.utils.HelpUtil;
import com.kgcx.core.utils.JsonUtil;
import com.kgcx.core.utils.LngLatUtil;
import com.kgcx.dbutils.core.model.ParameterMap;
import com.kgcx.dbutils.core.model.RowMap;
import com.kgcx.schedule.domain.DriverInfoDomain;
import com.kgcx.schedule.domain.ScheduleRecordDomain;
import com.kgcx.schedule.domain.ScheduleReserveCfgDetailDomain;
import com.kgcx.schedule.model.ScheduleException;
import com.kgcx.schedule.model.ScheduleRound;
import com.kgcx.schedule.rabbitmq.producer.OrderScheduledProducer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;

import java.sql.Timestamp;
import java.util.*;
import java.util.stream.Collectors;

public abstract class ScheduleOrderService {
    private static final Logger LOG = LoggerFactory.getLogger(ScheduleOrderService.class);

    @Autowired
    protected ScheduleSettingsService scheduleSettingsService;
    @Autowired
    protected ScheduleReserveCfgService scheduleReserveCfgService;
    @Autowired
    protected ScheduleRecordService scheduleRecordService;
    @Autowired
    private ScheduleNotifyService scheduleNotifyService;
    @Autowired
    protected DriverStateService driverStateService;
    @Autowired
    protected OrderScheduledProducer orderScheduledProducer;

    @Autowired
    private GaodeApi gaodeApi;
    @Autowired
    private DriverClient driverClient;
    @Autowired
    private MileageLocationClient mileageLocationClient;

    // @Transactional 这里因为需要锁定派单记录，所以不能开启事务，所有对数据的修改都需要立即写表
    public void addRecord(ScheduleRecordDomain record) {
        LOG.info(String.format("订单[%s]，创建派单记录。", record.getOrder_no()));

        prepareRecord(record);

        // 调度派车时间所对应时段的预约运力配置
        ScheduleReserveCfgDetailDomain cfgDetail = getReserveCfgDetail(record);

        try {
            // 检查是否可以下单成功
            checkRecord(record, cfgDetail);
        } catch (ScheduleException e) {
            // 发送创建派单记录失败的MQ消息
            record.setSchedule_error_code(e.getCode());
            record.setSchedule_error_msg(e.getMessage());
            orderScheduledProducer.addFailed(record);
            return;
        } catch (Exception e) {
            // 发送创建派单记录失败的MQ消息
            LOG.error(String.format("订单[%s]，创建派单记录异常！", record.getOrder_no()), e);

            record.setSchedule_error_code(ErrorCode.SCHEDULE_UNKNOWN_EXCEPTION);
            record.setSchedule_error_msg(HelpUtil.exceptionToString(e));
            orderScheduledProducer.addFailed(record);
            return;
        }

        // 把派单记录写表
        scheduleRecordService.insertEntity(record);

        // 发送创建派单记录成功的MQ消息
        orderScheduledProducer.addSuccessful(record);

        // 执行写表后的动作，比如立即类订单时，开始执行调度派单；预约类订单时，发送预约成功的消息
        afterRecordAdded(record, cfgDetail);
    }

    /**
     * 由定时任务发起的调度
     *
     * @param record
     */
    // @Transactional 这里因为需要锁定派单记录，所以不能开启事务，所有对数据的修改都需要立即写表
    public void scheduleRecord(ScheduleRecordDomain record) {
        LOG.info(String.format("订单[%s]， 定时任务自动派车。", record.getOrder_no()));

        // 调度派车时间所对应时段的预约运力配置
        ScheduleReserveCfgDetailDomain cfgDetail = getReserveCfgDetail(record);
        // 获取对应城市、用车类型的车辆/司机列表
        List<Driver> driverList = getDriverList(record);

        scheduleRecord(record, cfgDetail, driverList);
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    /**
     * 根据订单类型填写是否需要定时调度、以及定时调度的时间
     *
     * @param record
     */
    protected void prepareRecord(ScheduleRecordDomain record) {
        // 计算调度派车时间，后面判断运力数的时候，是根据调度派车时间来，而不是根据用车时间来。
        record.setSchedule_count(0);
        record.setSchedule_status(ScheduleRecordDomain.STATUS_PENDING);
        record.setCreate_time(HelpUtil.getNowTime());
    }

    /**
     * 获取对应时段的预约运力配置
     *
     * @param record
     * @return
     */
    protected ScheduleReserveCfgDetailDomain getReserveCfgDetail(ScheduleRecordDomain record) {
        ParameterMap params = new ParameterMap("status", Cons.COMMON_STATUS_ACTIVE).put("city_id", record.getCity_id())
                .put("auto_type", record.getAuto_type());
        List<ScheduleReserveCfgDetailDomain> cfgDetailList = scheduleReserveCfgService.selectDetailListPage(params);
        if (!HelpUtil.isEmpty(cfgDetailList)) {
            String time = HelpUtil.formatDatetime(record.getLatest_schedule_time(), "HH:mm:ss");
            for (ScheduleReserveCfgDetailDomain detail : cfgDetailList) {
                if (time.compareTo(detail.getBegin_time()) >= 0 && time.compareTo(detail.getEnd_time()) < 0) {
                    return detail;
                }
            }
        }

        return null;
    }

    /**
     * 检查派单记录是否可以生成
     *
     * @param record
     * @param cfgDetail
     * @throws ScheduleException
     */
    protected abstract void checkRecord(ScheduleRecordDomain record, ScheduleReserveCfgDetailDomain cfgDetail)
            throws ScheduleException;

    /**
     * 派单记录生成后需要执行的动作
     *
     * @param record
     * @param cfgDetail
     * @throws ScheduleException
     */
    protected abstract void afterRecordAdded(ScheduleRecordDomain record, ScheduleReserveCfgDetailDomain cfgDetail)
            throws ScheduleException;

    /**
     * 调用车辆/司机模块微服务获取空闲车辆/司机列表
     *
     * @param record
     * @return
     */
    protected List<Driver> getDriverList(ScheduleRecordDomain record) {
        LOG.info(String.format("订单[%s]，获取空闲车辆列表", record.getOrder_no()));

        if (DriverStateService.SWITCH) {
            ParameterMap params = new ParameterMap();
            params.put("city_id", record.getCity_id());
            params.put("auto_type", record.getAuto_type());

            List<DriverInfoDomain> driverInfos = driverStateService.getIdleDriverList(params);
            List<Driver> dirvers = driverInfos.stream().map(driverInfo -> {
                Driver driver = new Driver();
                driver.setCityId(driverInfo.getCity_id());
                driver.setAutoId(driverInfo.getAuto_id());
                driver.setAutoNo(driverInfo.getAuto_no());
                driver.setVehiclePurpose(driverInfo.getAuto_type());
                driver.setKid(driverInfo.getDriver_id());
                driver.setName(driverInfo.getDriver_name());
                driver.setPhone(driverInfo.getDriver_phone());
                driver.setLicenseId(driverInfo.getDriver_license_id());
                driver.setState(driverInfo.getDriver_state());
                driver.setLastUpdateStateTime(driverInfo.getState_update_time());
                driver.setAirportId(driverInfo.getAirport_id());
                driver.setStationId(driverInfo.getStation_id());
                return driver;
            }).collect(Collectors.toList());

            return dirvers;
        }

        try {

            String stateIn = HelpUtil.joinToInString(Cons.DRIVER_STATE_ON, Cons.DRIVER_STATE_STAY,
                    Cons.DRIVER_STATE_PREIN, Cons.DRIVER_STATE_IN);

            List<Driver> driverList = driverClient.getDriverList(record.getCity_id().intValue(), record.getAuto_type(), stateIn);
            if (HelpUtil.isEmpty(driverList)) {
                return new ArrayList<>();
            }

            return driverList;
        } catch (ScheduleException e) {
            throw e;
        } catch (Exception e) {
            LOG.error(String.format("订单[%s]，获取空闲车辆失败，其他异常！", record.getOrder_no()), e);
            throw new ScheduleException(ErrorCode.SCHEDULE_GET_DRIVER_FAILED, "网络异常！", e);
        }
    }

    /**
     * 检查当前时段运力数是否已满（预约）
     *
     * @param record
     * @param cfgDetail
     */
    protected void checkReserveQuantity(ScheduleRecordDomain record, ScheduleReserveCfgDetailDomain cfgDetail) {
        // 获取运力数
        if (cfgDetail == null) {
            LOG.warn(String.format("订单[%s]，预约运力数不足！未找到对应时段的预约运力数配置。", record.getOrder_no()));
            throw new ScheduleException(ErrorCode.SCHEDULE_NO_AVAILABLE_AUTO, "预约运力数不足！");
        }
        Integer quantity = cfgDetail.getQuantity();
        if (quantity == null || quantity <= 0) {
            LOG.warn(String.format("订单[%s]，预约运力数不足！[%s ~ %s]配置的预约运力数为[%d]。", record.getOrder_no(),
                    cfgDetail.getBegin_time(), cfgDetail.getEnd_time(), quantity));
            throw new ScheduleException(ErrorCode.SCHEDULE_NO_AVAILABLE_AUTO, "预约运力数不足！");
        }

        // 获取预约单总数（不包含立即派车的预约接机、接站）
        Integer scheduleRecordCount = getReserveRecordCount(record, cfgDetail, false); // 获取预约单总数
        if (quantity <= scheduleRecordCount) {
            LOG.warn(String.format("订单[%s]，预约运力数不足！[%s ~ %s]配置的预约运力数为[%d]，当前调度时段已有预约单数为[%d]。", record.getOrder_no(),
                    cfgDetail.getBegin_time(), cfgDetail.getEnd_time(), quantity));
            throw new ScheduleException(ErrorCode.SCHEDULE_NO_AVAILABLE_AUTO, "预约运力数不足！");
        }
    }

    /**
     * 检查当前时段运力数是否已满（立即）
     *
     * @param record
     * @param cfgDetail
     * @param availableAutoNum 空闲车辆数
     */
    protected void checkReserveQuantity(ScheduleRecordDomain record, ScheduleReserveCfgDetailDomain cfgDetail,
                                        Integer availableAutoNum) {
        // 无空闲车辆时
        if (availableAutoNum <= 0) {
            LOG.warn(String.format("订单[%s]，当前没有可用车辆！空闲车辆数为[0]。", record.getOrder_no()));
            throw new ScheduleException(ErrorCode.SCHEDULE_NO_AVAILABLE_AUTO, "当前没有可用车辆！");
        }

        // 获取运力数
        Integer quantity = cfgDetail == null ? 0 : cfgDetail.getQuantity();
        if (availableAutoNum > quantity) { // 空闲车辆数大于运力数时，直接返回，不需要再判断
            return;
        }

        // 只有当空闲车辆数小于等于运力数时，才需要进一步获取已派车的预约单数（不包含立即派车的预约接机、接站）
        Integer scheduledCount = getReserveRecordCount(record, cfgDetail, true); // 获取已派车的预约单数
        if (availableAutoNum <= (quantity - scheduledCount)) { // 空闲车辆数少于预留出来给预约单派车的数量时
            LOG.warn(String.format("订单[%s]，当前没有可用车辆！[%s ~ %s]配置的预约运力数为[%d]，当前调度时段已派车的预约单数为[%d]，空闲车辆/司机数为[%d]。",
                    record.getOrder_no(), cfgDetail.getBegin_time(), cfgDetail.getEnd_time(), quantity, scheduledCount,
                    availableAutoNum));
            throw new ScheduleException(ErrorCode.SCHEDULE_NO_AVAILABLE_AUTO, "当前没有可用车辆！");
        }
    }

    /**
     * 获取预约派单记录数，筛选条件为城市ID、用车类型、调度时段、是否已派车
     *
     * @param record
     * @param cfgDetail
     * @return
     */
    protected Integer getReserveRecordCount(ScheduleRecordDomain record, ScheduleReserveCfgDetailDomain cfgDetail,
                                            Boolean alreadyScheduled) {
        ParameterMap params = new ParameterMap();
        params.put("city_id", record.getCity_id()); // 城市
        params.put("auto_type", record.getAuto_type()); // 用车类型
        params.put("onlyReserve", true); // 预约类的记录，不包含立即派单和预约立即派单的，即只包含首次调度时间大于创建时间的。
        if (alreadyScheduled) {
            params.put("driver_id_isNotNull", true); // 已指派司机
        }

        // 调度时段
        params.put("fisrt_schedule_time_begin",
                HelpUtil.truncateTimestamp(record.getFisrt_schedule_time(), "yyyy-MM-dd " + cfgDetail.getBegin_time()));
        params.put("fisrt_schedule_time_end",
                HelpUtil.truncateTimestamp(record.getFisrt_schedule_time(), "yyyy-MM-dd " + cfgDetail.getEnd_time()));

        return scheduleRecordService.selectRecordCount(params);
    }

    /**
     * 自动派车逻辑处理入口方法
     *
     * @param record
     * @param cfgDetail
     * @param driverList
     */
    protected void scheduleRecord(ScheduleRecordDomain record, ScheduleReserveCfgDetailDomain cfgDetail,
                                  List<Driver> driverList) {
        Integer nextScheduleCount = record.getSchedule_count() + 1; // 从开始计数
        LOG.info(String.format("订单[%s]，自动派车，当前为第[%d]轮派车。", record.getOrder_no(), nextScheduleCount));

        Driver driver = null;

        try {
            driver = chooseDriver(record, groupingDrivers(record, driverList));
            record.setSchedule_error_code(null);
            record.setSchedule_error_msg("");
        } catch (ScheduleException e) {
            driver = null;
            record.setSchedule_error_code(e.getCode());
            record.setSchedule_error_msg(e.getMessage());
        } catch (Exception e) {
            LOG.error(String.format("订单[%s]，自动派车异常！", record.getOrder_no()), e);

            driver = null;
            record.setSchedule_error_code(ErrorCode.SCHEDULE_UNKNOWN_EXCEPTION);
            record.setSchedule_error_msg(HelpUtil.exceptionToString(e));
        }

        // 派单轮次计数加1
        record.setSchedule_count(nextScheduleCount);
        if (driver == null) {
            // 获取下轮派单配置
            ScheduleRound round = getScheduleRound(record);
            if (round != null) {
                LOG.warn(String.format("订单[%s]，自动派车失败，当前为第[%d]轮派车，还有下一轮派车！", record.getOrder_no(), nextScheduleCount));

                record.setSchedule_task(Cons.BOOLEAN_TRUE);
                record.setLatest_schedule_time(
                        HelpUtil.addTime(HelpUtil.getNowTime(), Calendar.SECOND, round.getInterval()));
                record.setSchedule_status(ScheduleRecordDomain.STATUS_PENDING);
                scheduleRecordService.updateEntity(record);
            } else {
                LOG.warn(String.format("订单[%s]，自动派车失败，当前为第[%d]轮派车，没有下一轮派车！", record.getOrder_no(), nextScheduleCount));

                record.setSchedule_task(Cons.BOOLEAN_FALSE);
                record.setSchedule_status(ScheduleRecordDomain.STATUS_FAILED);
                scheduleRecordService.updateEntity(record);

                // 发送派单失败的MQ消息
                orderScheduledProducer.scheduleFailed(record);

                // 短信通知客户、移入历史表或者通知管理员手动派单等
                scheduleFailed(record, driver);
            }
        } else {
            LOG.info(String.format("订单[%s]，自动派车成功，当前为第[%d]轮派车。", record.getOrder_no(), nextScheduleCount));

            record.setSchedule_task(Cons.BOOLEAN_FALSE);
            record.setSchedule_status(ScheduleRecordDomain.STATUS_SCHEDULED);
            scheduleRecordService.updateEntity(record);

            if (DriverStateService.SWITCH) {
                driverStateService.orderScheduled(record.getDriver_id(), record.getOrder_no());
            }

            // 发送派单成功的MQ消息
            orderScheduledProducer.scheduleSuccessful(record);

            // 通知司机订单来了
            scheduleNotifyService.notifyDriver(record, driver);

            // 短信通知下单人
            scheduleSuccessful(record, driver);
        }
    }

    /**
     * 发送派单成功的MQ消息、短信通知客户和司机等
     *
     * @param record
     * @param driver
     */
    protected abstract void scheduleSuccessful(ScheduleRecordDomain record, Driver driver);

    /**
     * 更新推送司机信息给乘客的时间，改派司机后，需要设置推送时间和状态
     *
     * @param record
     * @param notifyTime
     */
    protected void updateNotifyTime(ScheduleRecordDomain record, Timestamp notifyTime) {
        record.setNotify_passenger_time(notifyTime);
        record.setSchedule_status(ScheduleRecordDomain.STATUS_SCHEDULED);
        scheduleRecordService.updateEntity(record);
    }

    protected void sendSuccessfulMSG(String phone, String msgId, ScheduleRecordDomain record) {
        if (HelpUtil.isEmpty(phone)) {
            LOG.error(String.format("订单[%s]，给下单人或乘车人发送派单成功通知短信失败，手机号码为空！", record.getOrder_no()));
            return;
        }
        if (HelpUtil.isEmpty(msgId)) {
            LOG.error(String.format("订单[%s]，给下单人或乘车人[%s]发送派单成功通知短信失败，短信模板ID为空！", record.getOrder_no(), phone));
            return;
        }

        try {
            ParameterMap params = new ParameterMap("orderNo", record.getOrder_no());
            params.put("driverName", record.getDriver_name()).put("driverPhone", record.getDriver_phone());
            params.put("autoNo", record.getAuto_no());
            SmsUtils.sendSms(phone, msgId, JsonUtil.toString(params));
            LOG.info(String.format("订单[%s]，给下单人或乘车人[%s]发送派单成功通知短信成功。", record.getOrder_no(), phone));
        } catch (Exception e) {
            LOG.error(String.format("订单[%s]，给下单人或乘车人[%s]发送派单成功通知短信失败，其他异常！", record.getOrder_no(), phone), e);
        }
    }

    /**
     * 发送派单失败的MQ消息、短信通知客户、移入历史表等
     *
     * @param record
     * @param driver
     */
    protected abstract void scheduleFailed(ScheduleRecordDomain record, Driver driver);

    protected void sendFailedMSGToAdmin(String phone, String msgId, ScheduleRecordDomain record) {
        if (HelpUtil.isEmpty(phone)) {
            LOG.error(String.format("订单[%s]，给线下管理人员发送自动派单失败通知短信失败，手机号码为空！", record.getOrder_no()));
            return;
        }
        if (HelpUtil.isEmpty(msgId)) {
            LOG.error(String.format("订单[%s]，给线下管理人员[%s]发送自动派单失败通知短信失败，短信模板ID为空！", record.getOrder_no(), phone));
            return;
        }

        ParameterMap params = new ParameterMap("orderNo", record.getOrder_no());
        SmsUtils.sendSms(phone, msgId, JsonUtil.toString(params));
    }

    /**
     * 按状态对司机进行分组
     *
     * @param driverList
     * @return
     */
    protected Map<Integer, List<Driver>> groupingDrivers(ScheduleRecordDomain record, List<Driver> driverList) {
        Map<Integer, List<Driver>> driverMap = new HashMap<Integer, List<Driver>>();
        driverMap.put(Cons.DRIVER_STATE_ON, new ArrayList<Driver>()); // 上线
        driverMap.put(Cons.DRIVER_STATE_STAY, new ArrayList<Driver>()); // 待命
        driverMap.put(Cons.DRIVER_STATE_PREIN, new ArrayList<Driver>()); // 入栈中
        driverMap.put(Cons.DRIVER_STATE_IN, new ArrayList<Driver>()); // 入栈

        for (Driver driver : driverList) {
            driverMap.get(driver.getState()).add(driver);
        }

        return driverMap;
    }

    /**
     * 根据订单类型对应的调度派单规则挑选出合适的司机
     *
     * @param record
     * @param driverMap
     * @return
     * @throws ScheduleException
     */
    protected abstract Driver chooseDriver(ScheduleRecordDomain record, Map<Integer, List<Driver>> driverMap);

    /**
     * 按入栈排队顺序挑选入栈司机
     *
     * @param record
     * @param driverMap
     * @return
     */
    protected Driver chooseStateInDriver(ScheduleRecordDomain record, Map<Integer, List<Driver>> driverMap) {
        List<Driver> driverList = driverMap.get(Cons.DRIVER_STATE_IN);
        if (HelpUtil.isEmpty(driverList)) {
            LOG.info(String.format("订单[%s]，入栈车辆/司机列表为空！", record.getOrder_no()));
            return null;
        }

        sortDriverListByLastUpdateStateTime(driverList);

        for (Driver driver : driverList) {
            if (lockRecord(record, driver)) {
                return driver;
            }
        }

        return null;
    }

    /**
     * 按入栈中状态变更顺序挑选入栈中司机
     *
     * @param record
     * @param driverMap
     * @return
     */
    protected Driver chooseStatePreInDriver(ScheduleRecordDomain record, Map<Integer, List<Driver>> driverMap) {
        List<Driver> driverList = driverMap.get(Cons.DRIVER_STATE_PREIN);
        if (HelpUtil.isEmpty(driverList)) {
            LOG.info(String.format("订单[%s]，入栈中车辆/司机列表为空！", record.getOrder_no()));
            return null;
        }

        sortDriverListByLastUpdateStateTime(driverList);

        for (Driver driver : driverList) {
            if (lockRecord(record, driver)) {
                return driver;
            }
        }

        return null;
    }

    /**
     * 按待命排队顺序挑选待命司机
     *
     * @param record
     * @param driverMap
     * @return
     */
    protected Driver chooseStateStayDriver(ScheduleRecordDomain record, Map<Integer, List<Driver>> driverMap) {
        List<Driver> driverList = driverMap.get(Cons.DRIVER_STATE_STAY);
        if (HelpUtil.isEmpty(driverList)) {
            LOG.info(String.format("订单[%s]，待命车辆/司机列表为空！", record.getOrder_no()));
            return null;
        }

        sortDriverListByLastUpdateStateTime(driverList);

        for (Driver driver : driverList) {
            if (lockRecord(record, driver)) {
                return driver;
            }
        }

        return null;
    }

    protected void sortDriverListByLastUpdateStateTime(List<Driver> driverList) {
        driverList.sort((arg0, arg1) -> {
                    try {
                        if (arg0.getLastUpdateStateTime().before(arg1.getLastUpdateStateTime())) {
                            return -1;
                        } else if (arg0.getLastUpdateStateTime().after(arg1.getLastUpdateStateTime())) {
                            return 1;
                        } else {
                            return 0;
                        }
                    } catch (Exception e) {
                        LOG.warn(String.format("比较司机%d - %d状态更新时间失败！", arg0.getKid(), arg1.getKid()));
                        return 1;
                    }
                }
        );
    }

    /**
     * 场外在线车辆派单，根据派单轮次挑选出X公里范围内的最近车辆
     *
     * @param record
     * @param driverMap
     * @return
     */
    protected Driver chooseStateOnDriver(ScheduleRecordDomain record, Map<Integer, List<Driver>> driverMap) {
        // 获取派单轮次设置
        ScheduleRound round = getScheduleRound(record);
        if (round == null) {
            LOG.info(String.format("订单[%s]，未获取到派单轮次配置！", record.getOrder_no()));
            return null;
        }

        List<Driver> list = driverMap.get(Cons.DRIVER_STATE_ON);
        if (HelpUtil.isEmpty(list)) {
            LOG.info(String.format("订单[%s]，上线车辆/司机列表为空！", record.getOrder_no()));
            return null;
        }

        return chooseDriverByDistance(record, list);

    }

    protected Driver chooseDriverByDistance(ScheduleRecordDomain record, List<Driver> list) {
        // 获取派单轮次设置
        ScheduleRound round = getScheduleRound(record);
        // 获取场外车辆/司机的实时坐标
        Map<String, RowMap> driverLocationMap = getDriversLocation(record, list);

        // 根据司机坐标，计算距离，筛选出X米范围内的司机
        Integer range = round.getRange();
        List<Driver> driverList = new ArrayList<>();
        for (Driver driver : list) {
            RowMap location = driverLocationMap.get(String.valueOf(driver.getKid()));
            if (location == null) {
                LOG.info(String.format("订单[%s]，忽略司机[%d]，未获取到司机当前位置。", record.getOrder_no(), driver.getKid()));
                continue; // 未获取到经纬度的车辆/司机忽略掉
            }

            driver.setLng(location.getString("lng"));
            driver.setLat(location.getString("lat"));

            Integer distance = calculateDistance(record, driver);
            if (distance <= range) {
                driver.setDistance(distance);
                driverList.add(driver);
                LOG.info(String.format("订单[%s]，找到司机[%d]，司机[%s,%s]距乘客[%s,%s][%d]米，符合筛选范围[%d]米！", record.getOrder_no(),
                        driver.getKid(), driver.getLng(), driver.getLat(), record.getPlan_orig_lng(),
                        record.getPlan_orig_lat(), distance, range));
            } else {
                LOG.info(String.format("订单[%s]，忽略司机[%d]，司机[%s,%s]距乘客[%s,%s][%d]米，不符合筛选范围[%d]米！", record.getOrder_no(),
                        driver.getKid(), driver.getLng(), driver.getLat(), record.getPlan_orig_lng(),
                        record.getPlan_orig_lat(), distance, range));
            }
        }

        if (HelpUtil.isEmpty(driverList)) {
            LOG.info(String.format("订单[%s]，符合筛选范围的上线车辆/司机列表为空！", record.getOrder_no()));
            return null;
        }

        driverList.sort((arg0, arg1) -> {
            if (arg0.getDistance() < arg1.getDistance()) {
                return -1;
            } else if (arg0.getDistance() > arg1.getDistance()) {
                return 1;
            } else {
                return 0;
            }
        });

        for (Driver driver : driverList) {
            if (lockRecord(record, driver)) {
                // 计算预计开始服务的时间，用于与用车时间比较，给出恰当的提示
                Map<String, String> result = gaodeApi.getGaodeRestapi(driver.getLng() + "," + driver.getLat(),
                        record.getPlan_orig_lng() + "," + record.getPlan_orig_lat(), 5);
                if ("OK".equals(result.get("msg"))) {
                    Integer duration = Integer.valueOf(result.get("duration")); // 单位秒
                    record.setDriver_arrive_time(HelpUtil.addTime(HelpUtil.getNowTime(), Calendar.SECOND, duration));
                } else {
                    LOG.warn(String.format("订单[%s]，调用高德接口计算预计开始服务时间失败！使用用车时间作为预计开始服务时间。\n%s", record.getOrder_no(),
                            result.get("msg")));
                    record.setDriver_arrive_time(record.getPlan_depart_time());
                }
                LOG.info(String.format("订单[%s]，确定派车司机[%d]，司机[%s,%s]距乘客[%s,%s][%d]米，符合筛选范围[%d]米！", record.getOrder_no(),
                        driver.getKid(), driver.getLng(), driver.getLat(), record.getPlan_orig_lng(),
                        record.getPlan_orig_lat(), driver.getDistance(), range));
                return driver;
            }
        }
        return null;
    }

    /**
     * 获取场外辆/司机的实时坐标
     *
     * @param record
     * @param list
     * @return
     */
    protected Map<String, RowMap> getDriversLocation(ScheduleRecordDomain record, List<Driver> list) {
        String driverIds = HelpUtil.joinToInString(list, Driver::getKid);

        return mileageLocationClient.getDriverLocation(driverIds);
    }

    /**
     * 计算车辆/司机到出发地的直线距离
     *
     * @param record
     * @param driver
     * @return
     */
    protected Integer calculateDistance(ScheduleRecordDomain record, Driver driver) {
        return LngLatUtil
                .getDistance(Double.valueOf(record.getPlan_orig_lat()), Double.valueOf(record.getPlan_orig_lng()),
                        Double.valueOf(driver.getLat()), Double.valueOf(driver.getLng()))
                .intValue();
    }

    /**
     * 锁定司机，更新派单记录的车辆/司机相关字段
     *
     * @param record
     * @param driver
     */
    protected Boolean lockRecord(ScheduleRecordDomain record, Driver driver) {
        try {
            record.setAuto_id(driver.getAutoId());
            record.setAuto_no(driver.getAutoNo());
            record.setDriver_id(driver.getKid());
            record.setDriver_name(driver.getName());
            record.setDriver_phone(driver.getPhone());
            // record.setDriver_arrive_time(null);
            record.setSchedule_status(ScheduleRecordDomain.STATUS_SCHEDULING);
            scheduleRecordService.updateEntity(record);

            LOG.info(String.format("订单[%s]，锁定司机[%d]成功。", record.getOrder_no(), record.getDriver_id()));
            return true;
        } catch (DuplicateKeyException e) { // 如果抛出了唯一键冲突异常，则司机已经被其他订单锁定了
            LOG.info(String.format("订单[%s]，锁定司机[%d]失败，已被其他订单锁定。", record.getOrder_no(), driver.getKid()));
        } catch (Exception e) { // 如果抛出了其他异常
            LOG.error(String.format("订单[%s]，锁定司机[%d]失败，其他异常！", record.getOrder_no(), driver.getKid()), e);
        }

        record.setAuto_id(null);
        record.setAuto_no(null);
        record.setDriver_id(null);
        record.setDriver_name(null);
        record.setDriver_phone(null);
        record.setDriver_arrive_time(null);
        record.setSchedule_status(ScheduleRecordDomain.STATUS_PENDING);
        return false;
    }



    /**
     * 获取派单轮次配置
     *
     * @param record
     * @return
     */
    protected abstract ScheduleRound getScheduleRound(ScheduleRecordDomain record);
}
